<template>
  <i
    >很多时候我们的点击时间都是在按钮上触发的，为了避免多次点击，我们经常使用loading来控制按钮是否可点击。
    但是在移动端或者一些个性化鲜明的网页，按钮并不能直接使用组件库来实现，更多的是需要我们单独使用css实现，这个时候并没有我们常用的laoding属性进行控制，所以debounce就派上用场了。</i
  >
  <p>
    我们需要一个防抖指令来控制我们的事件，首先他不应该只能适用click事件，其次很多时候我们是需要第一次点击立即触动的，而不是延时触发，还有延迟时间应该是可以自定义的！
  </p>
  <MarkdownBox :mdStr="mdStr"></MarkdownBox>
  <hr />
  <h3>点击试用</h3>
  <h5>没有防抖的按钮</h5>
  <div class="btn" @click="() => demoClick(1)">
    我是按钮{{ demoShow.demo1 }}
  </div>
  <h5>使用防抖的按钮（默认的500延迟，非立即执行）</h5>
  <div class="btn" v-debounce="() => demoClick(2)">
    我是按钮{{ demoShow.demo2 }}
  </div>
  <h5>使用防抖的按钮（1000延迟，非立即执行）</h5>
  <div class="btn" v-debounce.1000="() => demoClick(3)">
    我是按钮{{ demoShow.demo3 }}
  </div>
  <h5>使用防抖的按钮（立即执行）</h5>
  <div class="btn" v-debounce.immediate="() => demoClick(4)">
    我是按钮{{ demoShow.demo4 }}
  </div>
  <h5>使用防抖的按钮（鼠标移入）</h5>
  <div class="btn" v-debounce:mousemove="() => demoClick(5)">
    我是按钮{{ demoShow.demo5 }}
  </div>
  <h5>自定义防抖事件</h5>
  <div style="display: flex; align-items: center; flex-wrap: wrap">
    <div style="width: 300px; margin-right: 20px">
      事件名称 <br />
      <el-select v-model="debounceEventConfig.event">
        <el-option v-for="item in eventList" :key="item" :value="item">{{
          item
        }}</el-option>
      </el-select>
    </div>
    <div style="width: 300px; margin-right: 20px">
      防抖时间 <br />
      <el-input-number
        v-model="debounceEventConfig.wait"
        style="width: 100%"
      ></el-input-number>
    </div>
    <div style="width: 300px; margin-right: 20px">
      是否立即执行 <br />
      <el-switch v-model="debounceEventConfig.immediate"></el-switch>
    </div>
  </div>
  <h5>指令预览</h5>
  <div class="demo_box mb_8">
    <div>
      {{
        `v-debounce:${debounceEventConfig.event}.${debounceEventConfig.wait}${
          debounceEventConfig.immediate ? '.immediate' : ''
        }`
      }}
    </div>
  </div>

  <el-text type="warning">
    注意如果函数需要传参，请使用箭头函数的方式，例：v-debounce="() =>
    func(param)"<br />
    这里并没有对绑定自定义指令的updated钩子函数，所以如果涉及的指令参数或者函数的值的引用变化会不生效，但是考虑在实际使用中基本不会去动态修改指令参数和函数，所以不考虑添加update钩子函数
  </el-text>
</template>

<script setup>
import { reactive } from 'vue'
import MarkdownBox from '../../components/MarkdownBox.vue'
const eventList = [
  'click', // 鼠标点击元素时触发
  'mousemove', // 鼠标在元素上移动时触发
  'mouseover', // 鼠标指针移动到元素或其子元素上时触发
  'mouseout', // 鼠标指针离开元素或其子元素时触发
  'mouseenter', // 鼠标指针首次移动到元素上时触发（不冒泡）
  'mouseleave', // 鼠标指针离开元素时触发（不冒泡）
  'mousedown', // 鼠标在元素上按下时触发
  'mouseup', // 鼠标在元素上松开时触发
]
const demoShow = reactive({
  demo1: 0,
  demo2: 0,
  demo3: 0,
  demo4: 0,
  demo5: 0,
  demo6: 0,
})
const debounceEventConfig = reactive({
  event: 'click',
  wait: 500,
  immediate: false,
})
const demoClick = (num) => {
  demoShow['demo' + num]++
}

const mdStr = `
\`\`\`js
/**
 * 防抖指令（优化版）
 * 用法：v-debounce.500.immediate="handler" 或 v-debounce:click.500="handler"
 * 参数：
 *   - 数字参数：延迟时间（ms），默认500ms
 *   - 事件类型参数：如 "click"、"input" 等，默认"click"
 * 修饰符：
 *   - immediate: 是否立即执行，默认为false
 *
 * 注意事项：
 *   - 带参数的函数需用箭头函数：v-debounce="() => handle(item)"
 *   - 同一元素请勿重复使用
 */
const debounceDirective = {
  mounted(el, binding) {
    // 1. 解析参数：支持事件类型（如"click"）和延迟时间
    let eventType = binding.arg || 'click'; // 默认事件
    let wait = 500; // 默认延迟
    let immediate = false
    const func = binding.value;

    // 2. 解析修饰符和回调函数
    const configKeyList = Object.keys(binding.modifiers)
    configKeyList.forEach(key => {
      if (!isNaN(Number(key))) {
        wait = Number(key)
      } else if (key === 'immediate') {
        immediate = true
      }
    })
    // 校验回调函数有效性
    if (typeof func !== 'function') {
      console.warn('v-debounce 绑定的值必须是函数');
      return;
    }

    // 3. 创建防抖处理函数并绑定到元素
    el.__debounce_handler__ = debounce(func, wait, immediate, el, binding.instance);
    el.addEventListener(eventType, el.__debounce_handler__);
  },
  beforeUnmount(el, binding) {
    // 清除定时器和事件监听，避免内存泄漏
    const eventType = binding.arg || 'click';
    if (el.__debounce_timer__) {
      clearTimeout(el.__debounce_timer__);
      el.__debounce_timer__ = null;
    }
    if (el.__debounce_handler__) {
      el.removeEventListener(eventType, el.__debounce_handler__);
      el.__debounce_handler__ = null; // 彻底释放引用
    }
  }
};

// 防抖核心函数（独立提取，避免重复代码）
const debounce = (func, wait, immediate, el, instance) => {
  return function (event) {
    // 清除已有定时器
    if (el.__debounce_timer__) {
      clearTimeout(el.__debounce_timer__);
    }

    // 立即执行逻辑
    const shouldCallNow = immediate && !el.__debounce_timer__;
    if (shouldCallNow) {
      func.call(instance, event);
    }

    // 设置新定时器（延迟执行）
    el.__debounce_timer__ = setTimeout(() => {
      // 非立即执行时，延迟后调用
      if (!immediate) {
        func.call(instance, event);
      }
      el.__debounce_timer__ = null; // 执行后清空定时器
    }, wait);
  };
}

export default debounceDirective;
\`\`\`

`
</script>

<style lang="scss" scoped>
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 50px;
  background: #ff4653;
  color: #fff;
  border-radius: 30px;
  cursor: pointer;
}
.demo_box {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 300px;
  height: 50px;
  background-color: antiquewhite;
  cursor: pointer;
}
</style>
